// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/child/child_process.h"
#include "content/renderer/media/canvas_capture_handler.h"
#include "content/renderer/media/media_stream_video_capturer_source.h"
#include "media/base/limits.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebMediaStreamSource.h"
#include "third_party/WebKit/public/platform/WebMediaStreamTrack.h"
#include "third_party/WebKit/public/platform/WebSize.h"
#include "third_party/WebKit/public/web/WebHeap.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkRefCnt.h"

using ::testing::_;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::SaveArg;
using ::testing::Test;
using ::testing::TestWithParam;

namespace content {

namespace {

    static const int kTestCanvasCaptureWidth = 320;
    static const int kTestCanvasCaptureHeight = 240;
    static const double kTestCanvasCaptureFramesPerSecond = 55.5;

    static const int kTestCanvasCaptureFrameEvenSize = 2;
    static const int kTestCanvasCaptureFrameOddSize = 3;
    static const int kTestCanvasCaptureFrameColorErrorTolerance = 2;
    static const int kTestAlphaValue = 175;

    ACTION_P(RunClosure, closure)
    {
        closure.Run();
    }

} // namespace

class CanvasCaptureHandlerTest
    : public TestWithParam<testing::tuple<bool, int, int>> {
public:
    CanvasCaptureHandlerTest() { }

    void SetUp() override
    {
        canvas_capture_handler_.reset(
            CanvasCaptureHandler::CreateCanvasCaptureHandler(
                blink::WebSize(kTestCanvasCaptureWidth, kTestCanvasCaptureHeight),
                kTestCanvasCaptureFramesPerSecond, message_loop_.task_runner(),
                &track_));
    }

    void TearDown() override
    {
        track_.reset();
        blink::WebHeap::collectAllGarbageForTesting();
        canvas_capture_handler_.reset();

        // Let the message loop run to finish destroying the capturer.
        base::RunLoop().RunUntilIdle();
    }

    // Necessary callbacks and MOCK_METHODS for VideoCapturerSource.
    MOCK_METHOD2(DoOnDeliverFrame,
        void(const scoped_refptr<media::VideoFrame>&, base::TimeTicks));
    void OnDeliverFrame(const scoped_refptr<media::VideoFrame>& video_frame,
        base::TimeTicks estimated_capture_time)
    {
        DoOnDeliverFrame(video_frame, estimated_capture_time);
    }

    MOCK_METHOD1(DoOnVideoCaptureDeviceFormats,
        void(const media::VideoCaptureFormats&));
    void OnVideoCaptureDeviceFormats(const media::VideoCaptureFormats& formats)
    {
        DoOnVideoCaptureDeviceFormats(formats);
    }

    MOCK_METHOD1(DoOnRunning, void(bool));
    void OnRunning(bool state) { DoOnRunning(state); }

    // Verify returned frames.
    static sk_sp<SkImage> GenerateTestImage(bool opaque, int width, int height)
    {
        SkBitmap testBitmap;
        testBitmap.allocN32Pixels(width, height, opaque);
        testBitmap.eraseARGB(kTestAlphaValue, 30, 60, 200);
        return SkImage::MakeFromBitmap(testBitmap);
    }

    void OnVerifyDeliveredFrame(
        bool opaque,
        int expected_width,
        int expected_height,
        const scoped_refptr<media::VideoFrame>& video_frame,
        base::TimeTicks estimated_capture_time)
    {
        if (opaque)
            EXPECT_EQ(media::PIXEL_FORMAT_I420, video_frame->format());
        else
            EXPECT_EQ(media::PIXEL_FORMAT_YV12A, video_frame->format());

        EXPECT_EQ(video_frame->timestamp().InMilliseconds(),
            (estimated_capture_time - base::TimeTicks()).InMilliseconds());
        const gfx::Size& size = video_frame->visible_rect().size();
        EXPECT_EQ(expected_width, size.width());
        EXPECT_EQ(expected_height, size.height());
        const uint8_t* y_plane = video_frame->visible_data(media::VideoFrame::kYPlane);
        EXPECT_NEAR(74, y_plane[0], kTestCanvasCaptureFrameColorErrorTolerance);
        const uint8_t* u_plane = video_frame->visible_data(media::VideoFrame::kUPlane);
        EXPECT_NEAR(193, u_plane[0], kTestCanvasCaptureFrameColorErrorTolerance);
        const uint8_t* v_plane = video_frame->visible_data(media::VideoFrame::kVPlane);
        EXPECT_NEAR(105, v_plane[0], kTestCanvasCaptureFrameColorErrorTolerance);
        if (!opaque) {
            const uint8_t* a_plane = video_frame->visible_data(media::VideoFrame::kAPlane);
            EXPECT_EQ(kTestAlphaValue, a_plane[0]);
        }
    }

    blink::WebMediaStreamTrack track_;
    // The Class under test. Needs to be scoped_ptr to force its destruction.
    std::unique_ptr<CanvasCaptureHandler> canvas_capture_handler_;

protected:
    media::VideoCapturerSource* GetVideoCapturerSource(
        MediaStreamVideoCapturerSource* ms_source)
    {
        return ms_source->source_.get();
    }

    // A ChildProcess and a MessageLoopForUI are both needed to fool the Tracks
    // and Sources into believing they are on the right threads.
    base::MessageLoopForUI message_loop_;
    ChildProcess child_process_;

private:
    DISALLOW_COPY_AND_ASSIGN(CanvasCaptureHandlerTest);
};

// Checks that the initialization-destruction sequence works fine.
TEST_F(CanvasCaptureHandlerTest, ConstructAndDestruct)
{
    EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
    base::RunLoop().RunUntilIdle();
}

// Checks that the destruction sequence works fine.
TEST_F(CanvasCaptureHandlerTest, DestructTrack)
{
    EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
    track_.reset();
    base::RunLoop().RunUntilIdle();
}

// Checks that the destruction sequence works fine.
TEST_F(CanvasCaptureHandlerTest, DestructHandler)
{
    EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
    canvas_capture_handler_.reset();
    base::RunLoop().RunUntilIdle();
}

// Checks that VideoCapturerSource call sequence works fine.
TEST_P(CanvasCaptureHandlerTest, GetFormatsStartAndStop)
{
    InSequence s;
    const blink::WebMediaStreamSource& web_media_stream_source = track_.source();
    EXPECT_FALSE(web_media_stream_source.isNull());
    MediaStreamVideoCapturerSource* const ms_source = static_cast<MediaStreamVideoCapturerSource*>(
        web_media_stream_source.getExtraData());
    EXPECT_TRUE(ms_source != nullptr);
    media::VideoCapturerSource* source = GetVideoCapturerSource(ms_source);
    EXPECT_TRUE(source != nullptr);

    media::VideoCaptureFormats formats;
    EXPECT_CALL(*this, DoOnVideoCaptureDeviceFormats(_))
        .Times(1)
        .WillOnce(SaveArg<0>(&formats));
    source->GetCurrentSupportedFormats(
        media::limits::kMaxCanvas /* max_requesteed_width */,
        media::limits::kMaxCanvas /* max_requesteed_height */,
        media::limits::kMaxFramesPerSecond /* max_requested_frame_rate */,
        base::Bind(&CanvasCaptureHandlerTest::OnVideoCaptureDeviceFormats,
            base::Unretained(this)));
    ASSERT_EQ(2u, formats.size());
    EXPECT_EQ(kTestCanvasCaptureWidth, formats[0].frame_size.width());
    EXPECT_EQ(kTestCanvasCaptureHeight, formats[0].frame_size.height());
    media::VideoCaptureParams params;
    params.requested_format = formats[0];

    base::RunLoop run_loop;
    base::Closure quit_closure = run_loop.QuitClosure();
    EXPECT_CALL(*this, DoOnRunning(true)).Times(1);
    EXPECT_CALL(*this, DoOnDeliverFrame(_, _))
        .Times(1)
        .WillOnce(RunClosure(quit_closure));
    source->StartCapture(
        params, base::Bind(&CanvasCaptureHandlerTest::OnDeliverFrame, base::Unretained(this)),
        base::Bind(&CanvasCaptureHandlerTest::OnRunning, base::Unretained(this)));
    canvas_capture_handler_->sendNewFrame(
        GenerateTestImage(testing::get<0>(GetParam()),
            testing::get<1>(GetParam()),
            testing::get<2>(GetParam()))
            .get());
    run_loop.Run();

    source->StopCapture();
}

// Verifies that SkImage is processed and produces VideoFrame as expected.
TEST_P(CanvasCaptureHandlerTest, VerifyFrame)
{
    const bool opaque_frame = testing::get<0>(GetParam());
    const bool width = testing::get<1>(GetParam());
    const bool height = testing::get<1>(GetParam());
    InSequence s;
    media::VideoCapturerSource* const source = GetVideoCapturerSource(static_cast<MediaStreamVideoCapturerSource*>(
        track_.source().getExtraData()));
    EXPECT_TRUE(source != nullptr);

    base::RunLoop run_loop;
    EXPECT_CALL(*this, DoOnRunning(true)).Times(1);
    media::VideoCaptureParams params;
    source->StartCapture(
        params, base::Bind(&CanvasCaptureHandlerTest::OnVerifyDeliveredFrame, base::Unretained(this), opaque_frame, width, height),
        base::Bind(&CanvasCaptureHandlerTest::OnRunning, base::Unretained(this)));
    canvas_capture_handler_->sendNewFrame(
        GenerateTestImage(opaque_frame, width, height).get());
    run_loop.RunUntilIdle();
}

// Checks that needsNewFrame() works as expected.
TEST_F(CanvasCaptureHandlerTest, CheckNeedsNewFrame)
{
    InSequence s;
    media::VideoCapturerSource* source = GetVideoCapturerSource(static_cast<MediaStreamVideoCapturerSource*>(
        track_.source().getExtraData()));
    EXPECT_TRUE(source != nullptr);
    EXPECT_TRUE(canvas_capture_handler_->needsNewFrame());
    source->StopCapture();
    EXPECT_FALSE(canvas_capture_handler_->needsNewFrame());
}

INSTANTIATE_TEST_CASE_P(
    ,
    CanvasCaptureHandlerTest,
    ::testing::Combine(::testing::Bool(),
        ::testing::Values(kTestCanvasCaptureFrameEvenSize,
            kTestCanvasCaptureFrameOddSize),
        ::testing::Values(kTestCanvasCaptureFrameEvenSize,
            kTestCanvasCaptureFrameOddSize)));

} // namespace content
